/* ************************************************************************ */
/*                                                                          */
/*  Copyright (c)2005 Russell Weir | damonsbane _at_ gmail                  */
/*                                                                          */
/* This library is free software; you can redistribute it and/or            */
/* modify it under the terms of the GNU Lesser General Public               */
/* License as published by the Free Software Foundation; either             */
/* version 3.0 of the License, or (at your option) any later version.       */
/*                                                                          */
/* This library is distributed in the hope that it will be useful,          */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        */
/* Lesser General Public License or the LICENSE file for more details.      */
/*                                                                          */
/* ************************************************************************ */

/**
Parses any time in these formats:
2        time in hours
2:00     time in hours and minutes
15:00    24-hour format time (for times after noon)
1:28:14  time in hours, minutes, and seconds
-        equivalent to 0

Can be suffixed with:
w = time is local "wall clock" time,
s = time is local "standard" time,
u,g or z = time is universal time
absence of an indicator, wall clock time is assumed

time can be preceded with the minus (-) sign for negative values
TimeSpecification.value holds the number of seconds, signed.

Note: To use this class in flash 7 or 8, define NO_EREG using
the -D haxe compiler switch.
*/
package altdate;

enum TimeType {
	WALL;
	STANDARD;
	UNIVERSAL;
}

class TimeSpecification {
	public var sign(default,setSign)	: Int;
	public var hours(default,setHours) 	: Int;
	public var minutes(default,setMinutes) 	: Int;
	public var seconds(default,setSeconds) 	: Int;
	public var type(default,default)	: TimeType;
	public var value(default,setValue)	: Int;

	public function new() {
		Reflect.setField(this,"sign",1);
		type = UNIVERSAL;
		value = 0;
	}

	public function getSign() : Int { return sign; }
	public function getValue() : Int { return value; }
	public function getHours() : Int { return hours; }
	public function getMinutes() : Int { return minutes; }
	public function getSeconds() : Int { return seconds; }

	public function setSign(v : Int) : Int { if(v != 1 && v != -1) throw "Invalid sign"; sign = v; recalc(); return v; }
	public function setValue(v : Int) : Int { value = v; normalize(); return value; }
	public function setHours(v : Int) : Int { hours = v; recalc(); return v;}
	public function setMinutes(v : Int) : Int { hours = v; recalc(); return v; }
	public function setSeconds(v : Int) : Int { hours = v; recalc(); return v; }

	/**
		Returns a new TimeSpecification which is the absolute
		value of this one
	*/
	public function abs() : TimeSpecification {
		var ts : TimeSpecification = new TimeSpecification();
		ts.value = (hours * 3600) + (minutes * 60) + seconds;
		return ts;
	}

	/**
		[-]HH:MM:SS format
	*/
	public function toString() : String {
		return Tools.secondsToTime(value);
	}

	/**
		Returns a JulianDay value for this time on a specific
		year month and day, optionally given the location's
		utcOffset and Standard Time offset.
	*/
	public function toJulianDay(
			year:Int,
			month:Int,
			day:Int,
			?utcOffset:TimeSpecification,
			?stdOffset:TimeSpecification
		) : altdate.JulianDay
	{
		if(utcOffset == null) {
			utcOffset = new TimeSpecification();
			if(stdOffset == null)
				stdOffset = new TimeSpecification();
		}
		var offset = 0;
		switch(type) {
		case WALL:
			offset += utcOffset.getValue();
			offset += stdOffset.getValue();
		case STANDARD:
			offset += utcOffset.getValue();
		case UNIVERSAL:
		}
		var gdate = new altdate.Gregorian();
#if !PARSER_BUILD
		gdate.set(true,new tz.zones.etc.UTC(),year, month, day, hours, minutes, seconds);
#else true
		gdate.set(true,null,year, month, day, hours, minutes, seconds);
#end
		var julian = gdate.toJulianDay();
		julian.value = julian.value - (offset / 86400);
		julian.round();
		return julian;
	}

	/////////////////////////////////////////////////////////////////////////
	//                        Static methods                               //
	/////////////////////////////////////////////////////////////////////////
	/**
		Parse a text time spec. Returns a new TimeSpecification class.
		Time is in the format [-]HH[:MM[:SS]][w|s|u|g|z], but can also be
		just - to indicate a 0 time. Throws on any invalid time
		specification.
		Examples:
			27	= 27 hours
			-14:40	= negative 14 hours, 40 minutes
			-	= O hours, 0 minutes, 0 seconds
			1:70	= will normalize to 2 hours, 10 minutes
	*/
	public static function parse( atom:String ) : TimeSpecification {
		var rv = new TimeSpecification();
		if(atom == "-")
			return rv;

		if(atom.charAt(0) == "-") {
			rv.sign = -1;
			atom = atom.substr(1);
		}
		else if(atom.charAt(0) == "+") {
			atom = atom.substr(1);
		}
		var values : Array<String> = ["0","0","0"];
		var spec : String = "w";

#if NO_EREG
		var parts = atom.split(":");
		var x : Int = 0;
		while(parts.length > 0 && values.length < 4) {
			values[x] = parts.shift();
			x++;
		}
		var last = x-1;
		var s = values[last];
		var c = s.charAt(s.length-1);
		if(c == "w" || c == "s" || c == "u" || c == "g" || c == "z") {
			if(s.length > 1) s = s.substr(0,s.length-1); else s = "0";
			values[last] = s;
			spec = c;
		}

#else true
		var r = ~/^([0-9]+)(:([0-9]+)(:([0-9]+))?)?([wsugz])?$/;
		if(!r.match(atom))
			throw "Invalid time spec " + atom;
		try { values[0] =  r.matched(1); }
		catch(e:Dynamic) {}
		try { values[1] =  r.matched(3); }
		catch(e:Dynamic) {}
		try { values[2] =  r.matched(5); }
		catch(e:Dynamic) {}
		try { spec =  r.matched(6); }
		catch(e:Dynamic) { spec = "w"; }
#end
		rv.type = WALL;
		if(spec == 's')
			rv.type = STANDARD;
		else if(spec =='g' || spec == 'u' || spec == 'z')
			rv.type = UNIVERSAL;
		trace(rv.sign + " " + values + " spec: "+spec);
		rv.value = rv.sign * ((Std.parseInt(values[0]) * 3600) + (Std.parseInt(values[1]) * 60) + Std.parseInt(values[2]));
		return rv;
	}

	/////////////////////////////////////////////////////////////////////////
	//                        Internal methods                             //
	/////////////////////////////////////////////////////////////////////////
	function recalc() : Void {
		value = sign * ((hours * 3600) + (minutes * 60) + seconds);
		//normalize(); called when value is set
	}

	function normalize() : Void {
		var v : Int = Tools.int(Math.abs(value));
		Reflect.setField(this,"hours", Math.floor(v/3600));
		v = v % 3600;
		Reflect.setField(this,"minutes", Math.floor(v/60));
		Reflect.setField(this,"seconds", v % 60);
	}
}